﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace PhenomMsrTweaker
{
    /// <summary>
    /// Represents a set all interesting registers to show.
    /// </summary>
    public sealed partial class StatusControl : UserControl
    {
        private int _optimalWidth;
        private bool _modified;
        private ushort _isaAddress;
        private ushort _isaRegAddr;
        private ushort _isaRegData;
        private bool _isInitialized = false;
        ushort _backup13;   // register 0x13
	    ushort _backup14;   // register 0x14
        ushort[] _backups = new ushort[3]; // registers 0x15 to 0x17
        ushort[] _backupsTemp = new ushort[3]; 
        ushort[] _backupsVoltage = new ushort[9];
        uint[] _backupsFans = new uint[5];
        private int _index = 0; // 0

        /// <summary>
        /// Gets or sets the associated hardware P-state index (0-4).
        /// </summary>
        public int StatusIndex
        {
            get { return _index; }
            set
            {
                if (value < 0 || value > 5)
                    throw new ArgumentOutOfRangeException("StatusIndex");

                _index = value;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the control has been modified by the user
        /// since the last load/save operation.
        /// </summary>
        public bool IsModified
        {
            get { return _modified; }
        }

        public StatusControl()
        {
            InitializeComponent();

            _optimalWidth = 270;
            _modified = true;
            refreshButton.TabIndex = 0;
            refreshButton.Click += (s, e) => LoadFromHardware();
        }

        /// <summary>
        /// Returns the delta for the optimal width, based upon the number of cores.
        /// </summary>
        public int GetDeltaOptimalWidth()
        {
            return (_optimalWidth - this.Width);
        }

        /// <summary>
        /// Loads the P-state settings from each core's MSR.
        /// </summary>
        public void LoadFromHardware()
        {
            IOInterface();
            Initialize();
            Reg64CPU.Text = "63     59     55     51     47     43     39     35     31     27     23     19     15     11     7       3    0\n"
                + COFVidString() + "\n" + CPUPstate0() + "\n" + CPUPstate1() + "\n" + CPUPstate2() + "\n" + CPUPstate3() + "\n" + CPUPstate4() + "\n";
            Reg32NB.Text = "31     27     23     19     15     11     7       3    0\n7       3    0\n";
            PCIDevices.Text = ReadPWMRegs() + "\n" + DebugOutput() + "\n" + MaxPstate() + "\n" + GetHWvalues() + "\n";
            PStateReg1.Text = "";
            PStateReg2.Text = "";
            NbPStateReg1.Text = "";
            ClockReg.Text = "";
            BIOSReg.Text = "";
            RegLabel64CPU.Text = "Bit numbering\nCOFVID 0071\nP-State0 0064\nP-State1 0065\nP-State2 0066\nP-State3 0067\nP-State4 0068";
            RegLabel32NB.Text = "Bit numbering 32Bit\nBit numbering 8Bit";
            PCIDevicesLabel.Text = "Fan Main Ctrl\nFan Ctrl\nPWM1 Ctrl\nPWM1 Duty\nD0 00\nD? 20\nD? 50\nD1F0 90\nSMBus A0\n?? B0\nD18 C0\nSBTSI\nMSRC001_0061 P-State\nIT8721F Temp1\nIT8721F Temp2\nIT8721F Vdd\nIT8721F Fan1\n";
            RegLabel4.Text = "";
            RegLabel5.Text = "";
            RegLabel12.Text = "";
            RegLabel13.Text = "";
            RegLabel6.Text = "";
            RegLabel7.Text = "";
            RegLabel8.Text = "";
            RegLabel9.Text = "";
            RegLabel10.Text = "";
            RegLabel11.Text = "";
            _modified = false;
        }

        private string Convert32IntToHex(uint Value)
        {
            string[] tmp = new string[8];
            string conv = "";
            for (int i = 0; i < 8; i++)
            {
                uint tmpvalue = ((Value >> (i * 4)) & 0xF);
                if (tmpvalue < 10) tmp[i] = tmpvalue.ToString();
                else if (tmpvalue == 10) tmp[i] = "A";
                else if (tmpvalue == 11) tmp[i] = "B";
                else if (tmpvalue == 12) tmp[i] = "C";
                else if (tmpvalue == 13) tmp[i] = "D";
                else if (tmpvalue == 14) tmp[i] = "E";
                else if (tmpvalue == 15) tmp[i] = "F";
                else tmp[i] = "X";
            }

            for (int i = 0; i < 8; i++)
            {
                conv += tmp[7 - i];
                if ((i + 1) % 4 == 0) conv += " ";
            }
            conv += "h";
            return conv;
        }

        private ushort ReadWord(ushort regPort, ushort valPort, byte regIndex)
        {
            Program.Ols.WriteIoPortByte(regPort, regIndex);
            ushort value = (ushort)(Program.Ols.ReadIoPortByte(valPort) << 8);
            regIndex++;
            Program.Ols.WriteIoPortByte(regPort, regIndex);
            value |= Program.Ols.ReadIoPortByte(valPort);

            return value;
        }

        private ushort ReadByte(ushort regPort, ushort valPort, byte regIndex)
        {
            Program.Ols.WriteIoPortByte(regPort, regIndex);
            return Program.Ols.ReadIoPortByte(valPort);
        }

        private void WriteByte(ushort regPort, ushort valPort, byte regIndex, byte value)
        {
            Program.Ols.WriteIoPortByte(regPort, regIndex);
            Program.Ols.WriteIoPortByte(valPort, value);
        }


        public void IOInterface()
        {
            byte superIOReg = 0x2E; // port for the SuperIO register index
            byte superIOVal = 0x2F; // port for the SuperIO register value 
            // enter the MB PnP mode
            Program.Ols.WriteIoPortByte(superIOReg, 0x87);
            Program.Ols.WriteIoPortByte(superIOReg, 0x01);
            Program.Ols.WriteIoPortByte(superIOReg, 0x55);
            Program.Ols.WriteIoPortByte(superIOReg, 0x55);

            // check the device ID in registers 0x20 and 0x21
            uint deviceID = ReadWord(superIOReg, superIOVal, 0x20);
            if ((deviceID >> 8) != 0x87)
                throw new ArgumentOutOfRangeException("No IT87xxF chip found.");
            // select logical device #4, the environment controller, by writing 0x04 to register 0x07
            WriteByte(superIOReg, superIOVal, 0x07, 0x04);

            // read the environment controller's ISA address from registers 0x60 and 0x61
            // the register port for the controller is _isaAddress + 0x05, the value port _isaAddress + 0x06
            _isaAddress = ReadWord(superIOReg, superIOVal, 0x60);
            _isaRegAddr = (ushort)(_isaAddress + 0x05);
            _isaRegData = (ushort)(_isaAddress + 0x06);
            // exit the MB PnP mode
            WriteByte(superIOReg, superIOVal, 0x02, 0x02);
        }

        private string GetHWvalues()
        {
            string text = "";
            ushort temp1 = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x29));
            ushort temp2 = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x2a));
            ushort vddtmp = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x22));
            double vdd = vddtmp * 12;
            ushort tmplow = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x0d));
            ushort tmphigh = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x18));
            uint fan1 = 1350000 / ((uint)(tmplow | (tmphigh << 8)) * 2);
            text += temp1.ToString() + " °C\n" + temp2.ToString() + " °C\n" + vdd.ToString() + " mV\n" + fan1.ToString() + " RPM";
            return text;
        }

        public string ReadPWMRegs()
        {
            string text = "";
            ushort fanMainCtrl = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x13));
            for (int i = 0; i < 8; i++)
            {
                text += (fanMainCtrl >> (7 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "\n";
            ushort fanCtrl = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x14));
            for (int i = 0; i < 8; i++)
            {
                text += (fanCtrl >> (7 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "\n";
            ushort pwm1 = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x15));
            for (int i = 0; i < 8; i++)
            {
                text += (pwm1 >> (7 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "\n";
            ushort pwm1duty = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x63));
            for (int i = 0; i < 8; i++)
            {
                text += (pwm1duty >> (7 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            
            text += "";
            return text;
        }

        private void Initialize()
        {
            // fan controller's Main Control Register (index 0x13)
            _backup13 = ReadByte(_isaRegAddr, _isaRegData, 0x13);
            // enable the tachometer for all 3 fans, set the fans to be independent and enable SmartGuardian
            //WriteByte(_isaRegAddr, _isaRegData, 0x13, 0x77);

            // fan controller's Control Register (index 0x14)
            _backup14 = ReadByte(_isaRegAddr, _isaRegData, 0x14);
            // set safe (?) PWM values: active high polarity, 3 MHz base clock, 20% minimum duty, all 3 fans on
            //WriteByte(_isaRegAddr, _isaRegData, 0x14, 0xDF);

            // set all three fans to full speed
            for (byte i = 0; i < 3; i++)
            {
                _backups[i] = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x15 + i));
                //WriteByte(_isaRegAddr, _isaRegData, (byte)(0x15 + i), 0x7F);
            }

            //Temp Regs 0x29 [3]
            for (byte i = 0; i < 3; i++)
            {
                _backupsTemp[i] = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x29 + i));
                //WriteByte(_isaRegAddr, _isaRegData, (byte)(0x15 + i), 0x7F);
            }

            //Voltage Regs 0x20 [9]
            for (byte i = 0; i < 9; i++)
            {
                _backupsVoltage[i] = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x20 + i));
                //WriteByte(_isaRegAddr, _isaRegData, (byte)(0x15 + i), 0x7F);
            }

            //Fan Regs 0x0d [5]
            ushort tmplow = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x0d));
            ushort tmphigh = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x18));
            if ((tmplow | (tmphigh << 8)) > 0)
                _backupsFans[0] = 1350000 / ((uint)(tmplow | (tmphigh << 8)) * 2);
            else
                _backupsFans[0] = 0;
            tmplow = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x0d));
            tmphigh = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x19));
            if ((tmplow | (tmphigh << 8)) > 0) 
                _backupsFans[1] = 1350000 / ((uint)(tmplow | (tmphigh << 8)) * 2);
            else
                _backupsFans[1] = 0;
            tmplow = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x0e));
            tmphigh = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x1a));
            if ((tmplow | (tmphigh << 8)) > 0) 
                _backupsFans[2] = 1350000 / ((uint)(tmplow | (tmphigh << 8)) * 2);
            else
                _backupsFans[2] = 0;
            tmplow = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x80));
            tmphigh = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x81));
            if ((tmplow | (tmphigh << 8)) > 0) 
                _backupsFans[3] = 1350000 / ((uint)(tmplow | (tmphigh << 8)) * 2);
            else
                _backupsFans[3] = 0;
            tmplow = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x82));
            tmphigh = ReadByte(_isaRegAddr, _isaRegData, (byte)(0x83));
            if ((tmplow | (tmphigh << 8)) > 0) 
                _backupsFans[4] = 1350000 / ((uint)(tmplow | (tmphigh << 8)) * 2);
            else
                _backupsFans[4] = 0;
            
            _isInitialized = true;
        }
        public uint GetValueFromSBTSI()
        {
            //Program.Ols.WritePciConfig(0xC3, 0x1E8, 0x601);
            //uint settings = Program.Ols.ReadPciConfig(0xC3, 0x1EC);
            uint settings = Program.Ols.ReadPciConfig(0xC3, 0xE8); 
            return (settings & 0xFFFFFFFF);
        }

        public string DebugOutput()
        {
            //const ushort PCI_AMD_VENDOR_ID = 0x1022;
	        //const ushort PCI_AMD_10H_MISC_DEVICE_ID = 0x1203;
	        //const ushort PCI_AMD_11H_MISC_DEVICE_ID = 0x1303;
            //byte value;
	        //uint _pciAddress = Program.Ols.FindPciDeviceById(PCI_AMD_VENDOR_ID,PCI_AMD_10H_MISC_DEVICE_ID,
            string text = "";
            uint settings = Program.Ols.ReadPciConfig(0x00, 0x00);
            text += Convert32IntToHex(settings) + "\n";
            settings = Program.Ols.ReadPciConfig(0x20, 0x00);
            text += Convert32IntToHex(settings) + "\n";
            settings = Program.Ols.ReadPciConfig(0x50, 0x00);
            text += Convert32IntToHex(settings) + "\n";
            settings = Program.Ols.ReadPciConfig(0x90, 0x00);
            text += Convert32IntToHex(settings) + "\n";
            settings = Program.Ols.ReadPciConfig(0xA0, 0x00);
            text += Convert32IntToHex(settings) + "\n";
            settings = Program.Ols.ReadPciConfig(0xB0, 0x00);
            text += Convert32IntToHex(settings) + "\n";
            settings = Program.Ols.ReadPciConfig(0xC0, 0x00);
            text += Convert32IntToHex(settings) + "\n";
            settings = GetValueFromSBTSI();
            text += Convert32IntToHex(settings);
            return text;
        }

        public string MaxPstate()
        {
            string text = "";
            ulong msr = Program.Ols.ReadMsr(0xC0010061u);
            uint maxP = (uint)(msr >> 4 & 0x7);
            uint minP = (uint)(msr & 0x7);
            text += "MaxPState: " + maxP + " MinPState: " + minP;
            return text;
        }

        public string COFVidString()
        {
            string text = "";
            ulong msr = Program.Ols.ReadMsr(0xC0010071u);
            for (int i = 0; i < 64; i++)
            {
                text += (msr >> (63 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "";
            return text;
        }

        public string CPUPstate0()
        {
            string text = "";
            ulong msr = Program.Ols.ReadMsr(0xC0010064u);
            for (int i = 0; i < 64; i++)
            {
                text += (msr >> (63 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "";
            return text;
        }

        public string CPUPstate1()
        {
            string text = "";
            ulong msr = Program.Ols.ReadMsr(0xC0010065u);
            for (int i = 0; i < 64; i++)
            {
                text += (msr >> (63 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "";
            return text;
        }

        public string CPUPstate2()
        {
            string text = "";
            ulong msr = Program.Ols.ReadMsr(0xC0010066u);
            for (int i = 0; i < 64; i++)
            {
                text += (msr >> (63 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "";
            return text;
        }

        public string CPUPstate3()
        {
            string text = "";
            ulong msr = Program.Ols.ReadMsr(0xC0010067u);
            for (int i = 0; i < 64; i++)
            {
                text += (msr >> (63 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "";
            return text;
        }

        public string CPUPstate4()
        {
            string text = "";
            ulong msr = Program.Ols.ReadMsr(0xC0010068u);
            for (int i = 0; i < 64; i++)
            {
                text += (msr >> (63 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "";
            return text;
        }

        public string BIOSClock()
        {
            string text = "";
            Program.Ols.WritePciConfig(0x00, 0xE0, 0x013080F1);
            // value of interest: D0F0xE4_x0130_80F1
            uint settings = Program.Ols.ReadPciConfig(0x00, 0xE4);
            for (int i = 0; i < 32; i++)
            {
                text += (settings >> (31 - i) & 0x1).ToString();
                if ((i + 1) % 4 == 0) text += " ";
            }
            text += "";
            return text;
        }

    }
}